Skip to content

fix(core): 用户中断 reasoning 时丢弃未执行的工具调用 - #2471

Open
hanydd wants to merge 2 commits into
agentscope-ai:mainfrom
hanydd:feat/interrupt-tool-call
Open

fix(core): 用户中断 reasoning 时丢弃未执行的工具调用#2471
hanydd wants to merge 2 commits into
agentscope-ai:mainfrom
hanydd:feat/interrupt-tool-call

Conversation

@hanydd

@hanydd hanydd commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

fix: #2470

AgentScope-Java Version

2.0.1-SNAPSHOT

Description

修改概述

修复用户中断 ReActAgent 的 reasoning 阶段时,尚未执行的工具调用被写入 memory,导致后续请求携带孤立 tool_use、会话上下文无法恢复的问题。

问题原因

reasoning 产生的消息会在工具执行前写入 memory。用户中断恰好发生在工具调用流式生成期间,或 reasoning 已完成但 acting 尚未开始时,当前实现仍会保留其中的 ToolUseBlock。由于对应工具没有执行,memory 中不会产生配对的工具结果;重建 Agent 或发起下一轮模型请求时,严格校验消息历史的模型会拒绝这段上下文。

修复方案

  • 用户中断 reasoning 时,仅持久化已经生成的文本和 thinking 内容,丢弃所有尚未执行的 ToolUseBlock
  • 将正常 reasoning 消息的完整持久化延后到 acting 前的中断检查之后,覆盖“工具调用已生成、但 acting 尚未开始”的窗口。
  • 保持系统中断以及 PartialReasoningPolicy 的既有语义不变。

测试覆盖

  • reasoning 流式生成部分工具参数时发生用户中断。
  • reasoning 已生成完整工具调用、acting 开始前发生用户中断。
  • 用户中断纯文本 reasoning 时保留已生成文本。
  • 用户中断仅包含工具调用的 reasoning 时不持久化空消息。
  • 系统中断分别使用 PartialReasoningPolicy.SAVEDISCARD
  • gotoReasoning 时分别处理非空和空 reasoning 消息。
  • 基于中断后的 memory 重建 Agent,并在严格历史校验下继续对话。
  • 本地 JaCoCo 报告中,本 PR 修改的所有可执行行和条件分支均已覆盖。
  • 完整执行 agentscope-core 测试:2228 tests,0 failures,0 errors,8 skipped。

Checklist

Please check the following items before code is ready to be reviewed.

  • Code has been formatted with mvn spotless:apply
  • All tests are passing (mvn -pl agentscope-core test)
  • Javadoc comments are complete and follow project conventions
  • Related documentation has been updated (not applicable; this fix does not change the public API)
  • Code is ready for review

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@hanydd
hanydd marked this pull request as ready for review July 29, 2026 00:07
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #2471

fix(core): 用户中断 reasoning 时丢弃未执行的工具调用

Summary

Fixes a correctness issue where user-interrupted reasoning could persist orphaned ToolUseBlock entries into the conversation history, making subsequent model calls fail due to unresolvable tool-use references.

Analysis

Root cause: The reasoning message (including any in-flight ToolUseBlock) was persisted to memory before the acting phase. If a user interrupt arrived during the window between tool-call generation and acting start, the tool call was written to state without a corresponding ToolResultBlock, leaving the conversation in an unrecoverable state.

Fix approach — correct and minimal:

  1. persistInterruptedReasoningMessage() — new method that strips ToolUseBlock entries for USER interrupts while preserving text/thinking content. SYSTEM interrupts retain existing PartialReasoningPolicy semantics (unchanged).
  2. Deferred persistence — moves the contextMutable().add(eventMsg) call to after checkInterrupted() in the pre-acting path, closing the race window.
  3. withoutToolUseBlocks() — clean static helper that filters out tool-use blocks; returns null if nothing remains (avoiding empty message persistence).

Code quality:

  • Clean separation between user and system interrupt handling
  • No changes to public API or PartialReasoningPolicy contract
  • Proper null handling throughout
  • toList() (immutable) used correctly

Test coverage (420 lines, 7 scenarios):

  • Partial streaming tool call during user interrupt
  • Completed tool call before acting during user interrupt
  • Text-only reasoning preserved on user interrupt
  • Tool-call-only reasoning not persisted as empty message
  • System interrupt with SAVE and DISCARD policies
  • gotoReasoning with non-null and null reasoning messages
  • Agent rebuild from interrupted state with strict history validation

All 2228 core tests pass. CI is fully green.

Verdict: Approved ✅ — well-scoped fix with thorough test coverage.

@hanydd
hanydd force-pushed the feat/interrupt-tool-call branch from 7aefec4 to 02b5e71 Compare July 29, 2026 10:22
@hanydd
hanydd force-pushed the feat/interrupt-tool-call branch from 02b5e71 to c00e9f4 Compare July 30, 2026 16:39
@AgentScopeJavaBot AgentScopeJavaBot added bug Something isn't working area/core/agent Agent runtime, pipeline, hooks, plan labels Aug 11, 2026

@AgentScopeJavaBot AgentScopeJavaBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review

@AgentScopeJavaBot AgentScopeJavaBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review

@AgentScopeJavaBot AgentScopeJavaBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core/agent Agent runtime, pipeline, hooks, plan bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 用户中断 reasoning 时会持久化未执行的工具调用,导致会话上下文无法恢复

3 participants